/**
 * todo 实现双向数据绑定、计算属性、监听的实现原理
 */
// 收集依赖,防止内存泄漏
const targetMap = new WeakMap()
const effectStack = [] // 存储effect
// 收集完成依赖
function track (target, key) {
  // 初始化
  const effect = effectStack[effectStack.length - 1]
  // 需要收集
  if (effect) {
    let depMap = targetMap.get(target)
    if (depMap === undefined) { // 如果不存在
      depMap = new Map()
      // 就存入
      targetMap.set(target, depMap)
    }
    let dep = depMap.get(key)
    if (dep === undefined) {
      dep = new Set()
      depMap.set(key, dep)
    }

    // 上面完成初始化,下面就需要收集了
    // 双向存储
    if (!dep.has(effect)) {
      dep.add(effect) // 把effect放在dep里面 这就叫存储
      effect.deps.push(dep)
    }
  }
}

// 执行
function trigger (target, key, info) {
  const depMap = targetMap.get(target)
  if (depMap === undefined) return // 没有computeds副作用
  const effects = new Set()
  const computeds = new Set() // computed是一个特殊的computed
  if (key) {
    let deps = depMap.get(key)
    deps.forEach(effect => {
      if (effect.computed) {
        computeds.add(effect)
      } else {
        effects.add(effect)
      }
    })

    effects.forEach(effect => effect())
    computeds.forEach(computed => computed())
  }

}
/**
 * !双向数据绑定
 * */
const baseHandler = {
  // get、set、has、construct、apply、defineProperty、deleteProperty、getOwnPropertyDescriptor、setPrototypeOf
  get (target, key) {
    const ret = target[key]// 实际中使用Reflect.get
    // todo 收集依赖到全局的map
    track(target, key)
    return ret // 没进行判断还是不是对象，参考proxy.js
  },
  set (target, key, val) {
    const info = { oldValue: target[key], newValue: val}
    target[key] = val // 实际中使用Reflect.set
    // 这里拿到收集的effect,执行以下就ok了
    trigger(target, key, info)
  }
}
function reactive(target) {
  const observer = new Proxy(target, baseHandler)
  return observer
}

// 监听数据，改变值
function effect (fn, options = {}) {
  let e = createReactiveEffcet(fn, options)
  if (!options.lazy) {
    e()
  }
  return e
}

function createReactiveEffcet (fn, options) {
  const effect = function effect(...args) {
    return run(effect, fn, args)
  }
  effect.deps = []
  effect.computed = options.computed
  effect.lazy = options.lazy
  return effect
}
// 调度
function run (effect, fn, args) {
  if (effectStack.indexOf(effect) === -1) {
   try {
    effectStack.push(effect)
    return fn(...args)
   } finally {
     effectStack.pop()
   }
  }
}
// 计算属性
function computed (fn) {
  const runner = effect(fn, 
    {
      computed: true, 
      lazy: true
    }
  )
  return {
    effect: runner,
    get value () {
      return runner()
    }
  }
}